package com.gusto.engine.semsim.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;

import com.gusto.engine.beans.exceptions.PropertyAccessException;
import com.gusto.engine.beans.model.Res;
import com.gusto.engine.semsim.exceptions.ConfigException;
import com.gusto.engine.semsim.exceptions.SimilarityException;
import com.gusto.engine.semsim.measures.ResourceSimilarity;
import com.gusto.engine.semsim.service.SimilarityService;
import com.gusto.engine.semsim.util.ConfigUtils;
import com.gusto.engine.semsim.util.SimilarityUtils;
//import com.gusto.engine.storage.persistence.PersistencePlugin;
import com.opensymphony.oscache.base.Cache;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;

/**
 * <p>A facade that will be the point of interaction 
 * between clients and the semantic similarity engine</p>
 * <p>It's used to : 
 * <ul>
 *  <li>Contain the similarity measures, management of the configFile</li>
 *  <li>Caching capabilities</li>
 *  <li>Getting the similarities</li>
 *  <li>Configure the plugins : Persistence, ...</li>
 *  
 *  <li>configure the resource factory</li>
 *  <li>get the neighborhood</li>
 * </ul>
 * @author amokrane.belloui@gmail.com
 */
public class SimilarityServiceImpl implements SimilarityService {
	
	private Logger log = Logger.getLogger(getClass());
	
	private GeneralCacheAdministrator cacheAdministrator;
	private Cache cache;
	
	private ConfigUtils configUtils;
	private SimilarityUtils similarityUtils;
	
	private Map<String, ResourceSimilarity> contextSimilarities = new HashMap<String, ResourceSimilarity>();
	
	public SimilarityServiceImpl() {
		super();
		
		Properties props = new Properties();
		props.put("cache.memory", true);
		props.put("cache.capacity", "10000");
		props.put("cache.unlimited.disk", true);
		props.put("cache.algorithm", "com.opensymphony.oscache.base.algorithm.LRUCache");
		cacheAdministrator = new GeneralCacheAdministrator(props); 
		this.cache = cacheAdministrator.getCache();
		
		this.configUtils = new ConfigUtils();
		this.similarityUtils = new SimilarityUtils();
	}
	
	public void setCONFIG_FILE(String config_file) {
		configUtils.setConfigFile(config_file);
		log.info("Config file set to : " + config_file);
	}
	
	public String getCONFIG_FILE() {
		return configUtils.getConfigFile();
	}
	
	public void setCACHE_SIZE(String cache_size) {
		cacheAdministrator.setCacheCapacity(Integer.parseInt(cache_size));
		log.info("Cache size set to : " + this.cache.getCapacity());
	}
	
	public String getCACHE_SIZE() {
		return this.cache.getCapacity() + "";
	}
	
	@Deprecated
	public void setResourceSimilarityMap(Map<String, ResourceSimilarity> resourceSimilarity) {
		this.contextSimilarities = resourceSimilarity;
	}
	
	public void setContextSimilarities(Map<String, ResourceSimilarity> resourceSimilarity) {
		this.contextSimilarities = resourceSimilarity;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void checkDefinitionChanged() throws ConfigException {
		if (configUtils.getConfigFile() != null) {
			configUtils.loadProperties();
			Iterator<String> iter = contextSimilarities.keySet().iterator();
			while (iter.hasNext()) {
				String context = iter.next();
				boolean hasChanged = configUtils.checkDefinitionChanged(context, contextSimilarities.get(context));
				if (hasChanged) {
					log.debug("Context '" + context + "' has changed");
					// Clean in the persistence Plugin
					contextConfigChanged(context);
					// Flush Cache
					cache.flushAll(new Date());
				}
			}
			configUtils.saveProperties();
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void contextConfigChanged(String context) {
		
	}
	
	/**
	 * Only method that allows calculation of distances, 
	 * so that it can include plugin's for storing, sending, the information
	 * @param context
	 * @param resource1
	 * @param resource2
	 * @return similarity
	 * @throws SimilarityException
	 */
	public Double getSimilarity(String context, Res resource1, Res resource2) throws SimilarityException {
		long start = System.currentTimeMillis();
		if (resource1.toString().compareTo(resource2.toString()) < 0) {
			Res tmp = resource1;
			resource1 = resource2;
			resource2 = tmp;
		}
		
		Double sim;
		if (cache != null) {
			String key = "sim." + context + "." + resource1 + "." + resource2;
			try {
				sim = (Double) cache.getFromCache(key);
				log.debug("Load similarity from Cache");
			} 
			catch (com.opensymphony.oscache.base.NeedsRefreshException nre) {
				ResourceSimilarity resSim = contextSimilarities.get(context);
		    	if (resSim == null) {
	    			throw new SimilarityException("Context '" + context + "' doesn't exist.");
	    		}
				try {
			    	sim = similarityUtils.getSimilarity(context, resource1, resource2 /*, persistencePlugin*/, resSim);
					cache.putInCache(key, sim);
					log.debug("Calculate similarity with no cache");
			    } 
			    catch (Exception ex) {
			    	sim = (Double) nre.getCacheContent();
			        cache.cancelUpdate(key);
			        log.debug("Load similarity from Cache after Exception in Calculating Similarity", ex);
			    }
			}
			log.info("Similarity calculated in : " + (System.currentTimeMillis() - start));
		}
		else {
			ResourceSimilarity resSim = contextSimilarities.get(context);
    		if (resSim == null) {
    			throw new SimilarityException("Context '" + context + "' doesn't exist.");
    		}
			try {
	    		sim = similarityUtils.getSimilarity(context, resource1, resource2, resSim);
			}
	    	catch (PropertyAccessException e) {
				throw new SimilarityException("Cannot access property " + e);
			}
		}
		
	    return sim;
	}
	
	public void destroy() {
		cacheAdministrator.destroy();
	}
	
}
